/*
Copyright 2011 Johan Maasing

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package nu.zoom.catonine.fontchooser.impl;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.awt.font.TextAttribute;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextPane;
import javax.swing.JToggleButton;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import nu.zoom.catonine.fontchooser.FontChooser;
import nu.zoom.catonine.fontchooser.FontChooser.FontValuesListener;
import nu.zoom.catonine.stylerule.ChangeTracker;
import nu.zoom.swing.action.AbstractTypedAction;
import nu.zoom.swing.field.IntegerField;
import nu.zoom.swing.layout.VerticalPanel;
import nu.zoom.ui.Resources;
import nu.zoom.ui.Resources.ResourceNotFoundException;

/**
 * @author "Johan Maasing" &lt;johan@zoom.nu&gt;
 * 
 */
public class FontChooserPanel extends JPanel implements ChangeTracker {
	private static final long serialVersionUID = -235872476377798595L;
	private final Resources resources;
	private final FontChooserImpl owner;
	private final AtomicBoolean changed = new AtomicBoolean(false);
	private final AtomicBoolean bold = new AtomicBoolean(false);
	private final AtomicBoolean italic = new AtomicBoolean(false);
	private FontListItem currentFontItem;
	private int fontSize = 12;
	private Font font;
	private final Font defaultFont;

	public FontChooserPanel(final Resources resources,
			final FontChooserImpl owner, final Font startFont,
			final Font defaultFont) {
		super();
		this.resources = resources;
		this.owner = owner;
		this.defaultFont = defaultFont;
		if (startFont != null) {
			this.font = startFont;
			this.bold.set(startFont.isBold());
			this.italic.set(startFont.isItalic());
			this.currentFontItem = new FontListItem(startFont.getFamily(),
					false);
			this.fontSize = startFont.getSize();
		} else {
			this.currentFontItem = new FontListItem(
					FontListItem.SYNTHETIC_DEFAULT, true);
		}
	}

	void initGUI() throws ResourceNotFoundException {
		final SamplePanel samplePanel = new SamplePanel();
		this.owner.addFontValuesListener(samplePanel);
		if (this.font != null) {
			samplePanel.setFont(this.font);
		}

		final String[] availableFontFamilyNames = GraphicsEnvironment
				.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
		Arrays.sort(availableFontFamilyNames);
		Vector<FontListItem> items = new Vector<FontChooserPanel.FontListItem>();
		items.add(new FontListItem(FontListItem.SYNTHETIC_DEFAULT, true));
		for (String fontName : availableFontFamilyNames) {
			items.add(new FontListItem(fontName, false));
		}
		final JList fontNameList = new JList(items);
		// Init selection
		if (this.currentFontItem != null) {
			final ListModel listModel = fontNameList.getModel();
			for (int n = 0; n < listModel.getSize(); n++) {
				FontListItem item = (FontListItem) listModel.getElementAt(n);
				if (this.currentFontItem.equals(item)) {
					fontNameList.setSelectedIndex(n);
					break;
				}
			}
		} else {
			fontNameList.setSelectedIndex(0);
		}

		fontNameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		fontNameList.addListSelectionListener(new ListSelectionListener() {

			@Override
			public void valueChanged(ListSelectionEvent e) {
				currentFontItem = (FontListItem) fontNameList
						.getSelectedValue();
				changed.set(true);
				updateFont();
			}
		});
		final JScrollPane fontNameScroller = new JScrollPane(fontNameList);
		final JLabel sizeLabel = new JLabel(
				resources
						.getMessage("nu.zoom.catonine.fontchooser.setting.size"));
		IntegerField sizeField = new IntegerField(sizeLabel, 4) {
			private static final long serialVersionUID = -3646290648724161680L;

			@Override
			protected ValidationResult validateDocument() {
				ValidationResult validationResult = super.validateDocument();
				if (ValidationResult.PASSED.equals(validationResult)) {
					int value = Integer.parseInt(getText());
					if ((value < 8) || (value > 24)) {
						String failMessage;
						try {
							failMessage = resources
									.getMessage("nu.zoom.catonine.fontchooser.setting.size.validation");
						} catch (ResourceNotFoundException e) {
							failMessage = "Font size out of range";
						}
						validationResult = new ValidationResult(failMessage);
					}
				}
				return validationResult;
			}

			@Override
			protected void validationPassed() {
				super.validationPassed();
				fontSize = Integer.parseInt(getText());
				changed.set(true);
				updateFont();
			}

		};
		if (this.fontSize > 1) {
			sizeField.setText(Integer.toString(this.fontSize));
		}

		// Button panel
		JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
		JToggleButton italicButton = new JToggleButton(new ItalicAction(
				resources));
		italicButton.setSelected(this.italic.get());
		buttonPanel.add(italicButton);
		final JToggleButton boldButton = new JToggleButton(new BoldAction(
				resources));
		boldButton.setSelected(this.bold.get());
		buttonPanel.add(boldButton);

		final VerticalPanel fontPropertiesPanel = new VerticalPanel();
		fontPropertiesPanel.addRow(sizeLabel, sizeField);
		fontPropertiesPanel.addRow(this.resources
				.getMessage("nu.zoom.catonine.fontchooser.setting.fontprops"),
				buttonPanel);

		final JSplitPane fontSpecificationSplitter = new JSplitPane(
				JSplitPane.HORIZONTAL_SPLIT, fontNameScroller,
				fontPropertiesPanel);
		final JSplitPane mainSplitter = new JSplitPane(
				JSplitPane.VERTICAL_SPLIT, fontSpecificationSplitter,
				samplePanel);

		this.setLayout(new BorderLayout());
		this.add(mainSplitter, BorderLayout.CENTER);

		/*
		 * Stupid hack for the stupid divider implementation that does not allow
		 * to set proportional location because of some lazy stupid hack from
		 * the swing team. We'll queue the setting of the dividers and hope that
		 * the component has become visible and when this runnable executes. I
		 * mean, how gawdamn hard would it have been to honour the preferred
		 * location when drawing the component for the first time, lazy
		 * bastards.
		 */
		EventQueue.invokeLater(new Runnable() {

			@Override
			public void run() {
				fontSpecificationSplitter.setDividerLocation(0.5);
				mainSplitter.setDividerLocation(0.8);
			}
		});
	}

	@Override
	public boolean isChanged() {
		return this.changed.get();
	}

	@Override
	public void resetChangeStatus() {
		this.changed.set(false);
	}

	/**
	 * @return
	 */
	public Font getCurrentFontValues() {
		return this.font;
	}

	public static class SamplePanel extends JPanel implements
			FontValuesListener {
		private static final long serialVersionUID = 233180331110372656L;
		final JTextPane sampleText = new JTextPane();

		public SamplePanel() {
			super(new BorderLayout());
			this.sampleText
					.setText("1234-56-78,90: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tellus enim, pulvinar vehicula porta a, bibendum at metus. ");
			this.add(this.sampleText, BorderLayout.CENTER);
		}

		@Override
		public void fontValuesChanged(FontChooser source, Font font) {
			this.sampleText.setFont(font);
		}
	}

	class ItalicAction extends AbstractTypedAction {
		private static final long serialVersionUID = 2393393299735051962L;

		public ItalicAction(final Resources resources)
				throws ResourceNotFoundException {
			super();
			setName(resources
					.getMessage("nu.zoom.catonine.fontchooser.setting.italic"));
			setToolTip(resources
					.getMessage("nu.zoom.catonine.fontchooser.setting.italic.tt"));
			setIcon(resources
					.getIcon("nu.zoom.catonine.fontchooser.setting.italic.icon"));
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			italic.set(!italic.get());
			changed.set(true);
			updateFont();
		}
	}

	class BoldAction extends AbstractTypedAction {
		private static final long serialVersionUID = 2393393299735051962L;

		public BoldAction(final Resources resources)
				throws ResourceNotFoundException {
			super();
			setName(resources
					.getMessage("nu.zoom.catonine.fontchooser.setting.bold"));
			setToolTip(resources
					.getMessage("nu.zoom.catonine.fontchooser.setting.bold.tt"));
			setIcon(resources
					.getIcon("nu.zoom.catonine.fontchooser.setting.bold.icon"));
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			bold.set(!bold.get());
			changed.set(true);
			updateFont();
		}
	}

	public void updateFont() {
		if ((currentFontItem != null) && (!currentFontItem.synthetic)) {
			HashMap<TextAttribute, Object> attributes = new HashMap<TextAttribute, Object>();
			attributes.put(TextAttribute.FAMILY, currentFontItem.fontName);
			attributes.put(TextAttribute.SIZE, fontSize);
			attributes.put(TextAttribute.WEIGHT,
					(bold.get() ? TextAttribute.WEIGHT_BOLD
							: TextAttribute.WEIGHT_REGULAR));
			attributes.put(TextAttribute.POSTURE,
					italic.get() ? TextAttribute.POSTURE_OBLIQUE
							: TextAttribute.POSTURE_REGULAR);
			this.font = Font.getFont(attributes);
		} else {
			this.font = defaultFont;
		}
		this.owner.fireFontChanged(this.font);
	}

	static class FontListItem {
		static final String SYNTHETIC_DEFAULT = "<default>";
		final String fontName;
		final boolean synthetic;

		public FontListItem(String fontName, boolean synthetic) {
			super();
			this.fontName = fontName;
			this.synthetic = synthetic;
		}

		@Override
		public String toString() {
			return fontName;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result
					+ ((fontName == null) ? 0 : fontName.hashCode());
			result = prime * result + (synthetic ? 1231 : 1237);
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			FontListItem other = (FontListItem) obj;
			if (fontName == null) {
				if (other.fontName != null)
					return false;
			} else if (!fontName.equals(other.fontName))
				return false;
			if (synthetic != other.synthetic)
				return false;
			return true;
		}
	}
}
